package com.w.picture.utils;

import java.awt.*;
import java.util.regex.Pattern;

/**
 * 提供颜色相似度计算工具的方法。
 */
public class ColorSimilarUtils {

    private static final double MAX_DISTANCE = Math.sqrt(3 * Math.pow(255, 2));
    private static final Pattern HEX_PATTERN = Pattern.compile("^(#|0x)?([A-Fa-f0-9]{6})$");

    // 工具类不需要实例化
    private ColorSimilarUtils() {
    }

    /**
     * 计算两个颜色之间的欧几里得距离，并将其转换为相似度得分。
     *
     * @param color1 第一个颜色
     * @param color2 第二个颜色
     * @return 相似度得分（0到1之间，1表示完全相同）
     */
    public static double calculateSimilarity(Color color1, Color color2) {
        if (color1 == null || color2 == null) {
            throw new IllegalArgumentException("颜色参数不能为空");
        }

        int r1 = color1.getRed();
        int g1 = color1.getGreen();
        int b1 = color1.getBlue();

        int r2 = color2.getRed();
        int g2 = color2.getGreen();
        int b2 = color2.getBlue();

        // 使用更高效的数学运算计算欧氏距离
        // 欧几里得距离是两个点之间最短直线的距离。在这个上下文中，
        // 我们将RGB颜色空间视为三维空间，其中每个颜色分量（红、绿、蓝）都是一个维度。
        // 为了计算两个颜色之间的欧氏距离，我们需要在所有三个维度上计算差值，
        // 然后对这些差值进行平方和求和，最后取平方根以获得实际的距离。

        // 首先计算每个颜色分量之间的差值，并对它们进行平方。
        // 这样做是为了确保所有差异都为正值，并且较大差异的影响会被放大。
        double redDifferenceSquared = Math.pow(r1 - r2, 2);
        double greenDifferenceSquared = Math.pow(g1 - g2, 2);
        double blueDifferenceSquared = Math.pow(b1 - b2, 2);

        // 接下来，我们将这三个平方差相加，得到的是两个颜色在RGB空间中的距离的平方。
        // 因为我们不需要确切的距离值，而是需要基于这个距离来计算相似度，
        // 所以我们可以推迟开方操作直到必要时再执行，这可以提高计算效率。
        double distanceSquared = redDifferenceSquared + greenDifferenceSquared + blueDifferenceSquared;

        // 计算相似度得分
        // 相似度是基于颜色间的距离来定义的，距离越小，颜色越相似。
        // 在这里，我们采用线性转换的方式，将距离转换为0到1之间的相似度得分。
        // 当距离为0时，表示两个颜色完全相同，因此相似度为1；
        // 而当距离达到最大可能值时，相似度为0，表示两个颜色完全不同。
        // MAX_DISTANCE是我们之前定义的一个常量，它代表了RGB颜色空间中两个颜色的最大可能距离，
        // 即当一个颜色为黑色（R=0,G=0,B=0），另一个颜色为白色（R=255,G=255,B=255）时的距离。
        // 通过将实际距离除以最大可能距离，我们可以得到一个标准化的距离值，
        // 然后用1减去这个值就得到了相似度得分。
        double similarity = 1 - Math.sqrt(distanceSquared) / MAX_DISTANCE;

        // 确保相似度不会小于0或大于1
        return Math.max(0, Math.min(similarity, 1));
    }

    /**
     * 根据十六进制颜色代码计算相似度，支持带#或0x前缀的格式，或者不带任何前缀的六位十六进制数。
     *
     * @param hexColor1 第一个颜色的十六进制代码（如 "#FF0000", "0xFF0000" 或 "FF0000"）
     * @param hexColor2 第二个颜色的十六进制代码（如 "#FE0101", "0xFE0101" 或 "FE0101"）
     * @return 相似度得分（0到1之间，1表示完全相同）
     * @throws IllegalArgumentException 如果提供的十六进制字符串不是有效的颜色代码
     */
    public static double calculateSimilarity(String hexColor1, String hexColor2) {
        validateHexColor(hexColor1);
        validateHexColor(hexColor2);

        // 移除可能存在的#或0x前缀，并将剩余部分转换为颜色对象
        Color color1 = Color.decode("0x" + hexColor1.replaceFirst("^(#|0x)", ""));
        Color color2 = Color.decode("0x" + hexColor2.replaceFirst("^(#|0x)", ""));

        return calculateSimilarity(color1, color2);
    }

    /**
     * 验证给定的字符串是否为有效的十六进制颜色代码。
     *
     * @param hexColor 十六进制颜色字符串，可选带#或0x前缀
     * @throws IllegalArgumentException 如果提供的字符串不是有效的颜色代码
     */
    private static void validateHexColor(String hexColor) {
        if (!HEX_PATTERN.matcher(hexColor).matches()) {
            throw new IllegalArgumentException("无效的十六进制颜色代码: " + hexColor);
        }
    }

    // 示例代码
    public static void main(String[] args) {
        // 测试颜色
        try {
            Color color1 = Color.decode("0xFF0000"); // 红色
            Color color2 = Color.decode("0xFE0101"); // 浅红色
            double similarity = calculateSimilarity(color1, color2);

            System.out.println("颜色相似度为：" + similarity);

            // 测试十六进制方法
            double hexSimilarity = calculateSimilarity("#FF0000", "#FE0101"); // 浅红色和深红色
            System.out.println("十六进制颜色相似度为：" + hexSimilarity);
            hexSimilarity = calculateSimilarity("0xFF0000", "0xFE0101"); // 浅红色和深红色
            System.out.println("十六进制颜色相似度为：" + hexSimilarity);
        } catch (IllegalArgumentException e) {
            System.err.println(e.getMessage());
        }
    }
}
